package space.xiaoyuan.middleware.db.router.dynamic;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import space.xiaoyuan.middleware.db.router.DBContextHolder;
import space.xiaoyuan.middleware.db.router.annotation.DBRouterStrategy;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

//@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
//拦截          StatementHandler 的使用Connection.class, Integer.class的 prepare方法
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicMybatisPlugin implements Interceptor {
    private final Pattern pattern = Pattern.compile("(from|into|update)[\\s]{1,}(\\w{1,})", Pattern.CASE_INSENSITIVE);
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取statementHandler invocation拦截了类注解上的配置
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //调用MetaObject.forObject 操作statementHandler 里的属性，避免直接使用了java反射，提高可读性
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // 获取自定义注解判断是否进行分表操作
        //MappedStatement 的 ID 是由 namespace 和 statement 的 ID 组成的。它的格式通常是 namespace.statementId。
        //namespace 是 Mapper 接口的完全限定名，或者是 Mapper XML 文件的命名空间。
        //statementId 是 Mapper 接口中的方法名，或者是 Mapper XML 文件中 SQL 语句的 ID。
        String id = mappedStatement.getId();
        //把statementId去掉 即把方法名去掉，留下mapper接口的全限定名称
        String className = id.substring(0, id.lastIndexOf("."));
        Class<?> clazz = Class.forName(className);
        // 获取自定义注解判断是否进行分表操作
        DBRouterStrategy annotation = clazz.getAnnotation(DBRouterStrategy.class);

        //从 statementHandler获取出sql
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        if(null == annotation || !annotation.enableSplitTable()){
            //没有用DBRouterStrategy 或者 没开启分库分表 直接返回
            logger.info("logic & actual sql : {}",sql);
            return invocation.proceed();
        }
        //替换表名
        Matcher matcher = pattern.matcher(sql);
        String tableName = null;
        if (matcher.find()) {
            //匹配上了正则
            tableName = matcher.group().trim();
        }
        String actualSql = matcher.replaceAll(tableName + "_" + DBContextHolder.getTBKey());
        logger.info("actual database_idx : {},logic sql : {} \n actual sql : {}",
                DBContextHolder.getDBKey(),
                sql,
                actualSql);

        //获取字段
        Field sqlField = boundSql.getClass().getDeclaredField("sql");
        sqlField.setAccessible(true);
        //修改boundSql对象的“sql”字段为actualSql
        sqlField.set(boundSql,actualSql);
        sqlField.setAccessible(false);

        return invocation.proceed();
    }


}
